package com.gonsin.ta.logic.parse;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.gonsin.ta.logic.exceptions.TaException;
import com.gonsin.ta.logic.parse.condition.Condition;
import com.gonsin.ta.logic.parse.condition.SwitchCondition;
import com.gonsin.ta.logic.parse.condition.WaitCondition;
import com.gonsin.ta.logic.parse.operator.*;
import com.gonsin.ta.logic.process.SignalCenter;
import com.gonsin.ta.logic.process.TaBus;
import com.ql.util.express.*;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class TaChainBuilder {

    private Chain chain;

    //这是主体的Condition，不包含前置和后置
    //声明这个变量，而不是用chain.getConditionList的目的，是为了辅助平滑加载
    //虽然TaBus里面的map都是CopyOnWrite类型的，但是在buildCondition的时候，为了平滑加载，所以不能事先把chain.getConditionList给设为空List
    //所以在这里做一个缓存，等conditionList全部build完毕后，再去一次性替换chain里面的conditionList
    private final List<Condition> conditionList;

    //前置处理Condition，用来区别主体的Condition
    private final List<Condition> preConditionList;

    //后置处理Condition，用来区别主体的Condition
    private final List<Condition> finallyConditionList;

    //EL解析引擎
    private final ExpressRunner expressRunner;

    // 指令集缓存, 必须是线程安全的集合
    private final Map<String, InstructionSet> instructionSetMap;

    // 表达式的资源装载器
    private final IExpressResourceLoader expressResourceLoader;

    /**
     * 是否允许替换原来的流程
     */
    private boolean replace;

    public static TaChainBuilder createChain(SignalCenter signalCenter) {
        return new TaChainBuilder(signalCenter);
    }

    public TaChainBuilder(SignalCenter signalCenter) {
        chain = new Chain();
        conditionList = new ArrayList<>();
        preConditionList = new ArrayList<>();
        finallyConditionList = new ArrayList<>();
        instructionSetMap = new ConcurrentHashMap<>();
        expressResourceLoader = new DefaultExpressResourceLoader();

        //初始化QLExpress的Runner
        expressRunner = new ExpressRunner(false, false, expressResourceLoader, null, instructionSetMap);
        expressRunner.addFunction("RUN", new RunOperator(chain));
        expressRunner.addFunction("WAIT", new WaitOperator(chain, signalCenter));
        expressRunner.addFunction("SWITCH", new SwitchOperator(chain));
        expressRunner.addFunctionAndClassMethod("CASE", SwitchCondition.class, new CaseOperator(chain));
        expressRunner.addFunctionAndClassMethod("THEN", WaitCondition.class, new ThenOperator(chain));
        expressRunner.addFunctionAndClassMethod("TIMEOUT", WaitCondition.class, new TimeoutOperator(chain));

        this.replace = false;
    }

    //在parser中chain的build是2段式的，因为涉及到依赖问题，以前是递归parser
    //2.6.8之后取消了递归的模式，两段式组装，先把带有chainName的chain对象放进去，第二段再组装chain里面的condition
    //所以这里setChainName的时候需要判断下
    public TaChainBuilder setChainId(String chainId) {
        this.chain.setChainId(chainId);
        return this;
    }

    public TaChainBuilder setChainName(String chainName){
        this.chain.setChainName(chainName);
        return this;
    }

    public TaChainBuilder setFilename(String filename){
        this.chain.setFilename(filename);
        return this;
    }

    public TaChainBuilder setQL(String qlStr) {
        this.chain.setQlScript(qlStr);


        StringBuffer code = new StringBuffer();
        // 去除// 开头和#开头的文本
        Arrays.stream(qlStr.split("\\n")).filter(l -> {
            if(l.trim().startsWith("#")){
                return false;
            }
            if(l.trim().startsWith("//")){
                return false;
            }
            return true;
        }).forEach(l -> {
            code.append(l).append("\n");
        });

        qlStr = code.toString().trim();
        if (StrUtil.isBlank(qlStr)){
            throw new TaException("【{}】ta脚本内容不能为空", chain.getFilename());
        }

        List<String> errorList = new ArrayList<>();
        try{
            DefaultContext<String, Object> context = new DefaultContext<>();
            context.put("first", true);


            //这里一定要先放chain，再放node，因为node优先于chain，所以当重名时，node会覆盖掉chain
            //往上下文里放入所有的chain，是的el表达式可以直接引用到chain
            TaBus.getChainMap().values().forEach(chain -> context.put(chain.getChainId(), chain));
            TaBus.getNodeMap().values().forEach(node -> context.put(node.getNodeId(), node));

            //解析el成为一个Condition
            //为什么这里只是一个Condition，而不是一个List<Condition>呢
            //这里无论多复杂的，外面必定有一个最外层的Condition，所以这里只有一个，内部可以嵌套很多层，这点和以前的不太一样
            Condition condition = (Condition) expressRunner.execute(qlStr, context, errorList, true, true);

            // 处理futureCondition
            condition.handleThis(context);

            //把主要的condition加入
            this.conditionList.add(condition);
            return this;
        }catch (Exception e){
            for (String scriptErrorMsg : errorList){
                log.error("\n{}", scriptErrorMsg);
            }
            log.error(e.toString(), e);
            throw new TaException(e.getMessage());
        }
    }

    public void build() {
        this.chain.setConditionList(this.conditionList);
        checkBuild();

        // TODO 添加到缓存中
        TaBus.addChain(this.chain, replace);
    }

    /**
     * build 前简单校验
     */
    private void checkBuild() {
        List<String> errorList = new ArrayList<>();
        if (StrUtil.isBlank(this.chain.getChainId())) {
            errorList.add("<chain>标签必须要填id值");
        }
        if (CollUtil.isNotEmpty(errorList)) {
            throw new TaException(CollUtil.join(errorList, ","));
        }
    }

    public TaChainBuilder enableReplace(boolean enableReplace) {
        this.replace = enableReplace;
        return this;
    }
}
